package me.lubinn.Vicincantatio.Spells;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
//import java.util.Timer;
//import java.util.TimerTask;

import me.lubinn.Vicincantatio.Vicincantatio;
import net.minecraft.server.EntityFireball;
import net.minecraft.server.EntityLiving;
import net.minecraft.server.EntityTNTPrimed;

import org.bukkit.Chunk;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.TreeType;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftArrow;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.CreatureType;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Skeleton;
import org.bukkit.entity.Spider;
import org.bukkit.entity.Zombie;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.util.Vector;

import ru.tehkode.permissions.PermissionManager;
import ru.tehkode.permissions.bukkit.PermissionsEx;

public abstract class Spell
{
	//targeting parameters
	protected boolean targeted = true;
	protected double rangeBase = 10;
	protected double rangeGrowth = 5;
	
	//Future food usage
	protected int foodCost;

	
	static final Map<String, Spell> spells = new HashMap<String, Spell>();
	static
	{
		spells.put("salio", new Salio());
		spells.put("praemium", new Praemium());
		spells.put("fulmen", new Fulmen());
		spells.put("domus", new Domus());
		spells.put("pluvia", new Pluvia());
		spells.put("tempestas", new Tempestas());
		spells.put("sereno", new Sereno());
		spells.put("lucem", new Lucem());
		spells.put("noctem", new Noctem());
		spells.put("arbore", new Arbore());
		spells.put("chasma tempestas", new ChasmaTempestas());
		spells.put("foobar", new RealityCheck());
		spells.put("vorto", new Vorto());
		spells.put("urgeo", new Urgeo());
		spells.put("vello", new Vello());
		spells.put("libro", new Hover());
		spells.put("ostium tartarus", new OstiumTartarus());
		spells.put("chunkus erasus", new ChunkusErasus());
		spells.put("chunkus renero", new ChunkusRenero());
		spells.put("sagitto", new Sagitto());
		spells.put("sagitto pluvia", new SagittoPluvia());
		spells.put("remedium", new Remedium());
		spells.put("mutatio", new Mutatio());
		spells.put("hadoken", new Hadoken());
		spells.put("extinguere", new Extinguere());
		spells.put("globus ignis", new GlobusIgnis());
		spells.put("langueo", new Starve());
		spells.put("rapio", new leap());
		spells.put("ignis perpetua", new IgnisPerpetua());
		spells.put("impedio", new Root());
		spells.put("surge gigas", new SummonGiant());
	}
	
	static Random rand = new Random();
	
	protected int Range(int size) //gets the distance away to target this spell when augmented with flight
	{
		return (int)(rangeBase + size*rangeGrowth);
	}
	protected Vector MoveVector(int size) //where to move when spell augmented with motion
	{
		return new Vector(0, 1, 0);
	}
	public abstract Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments); //casts the spell, returns a reference to a copy for tracking

	public static Spell GetSpell(String spellName)
	{
		if (spells.containsKey(spellName))
			return spells.get(spellName);
		if (ConstructionSpell.spells.containsKey(spellName))
			return ConstructionSpell.spells.get(spellName); 
		//Vicincantatio.log.info("Spell "+spellName+" not found!");
		return null;
	}
	
	
	//Food Cost Stuffs
	protected boolean FoodEnabled()
	{
		if(Vicincantatio.config.getBoolean("food_enabled", true))
			return true;
		else
			return false;
	}

	
}

class Salio extends Spell
{
    public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
    {
    	if(FoodEnabled())
    	{
    		foodCost = 1;
    		if(event.getPlayer().getFoodLevel() <= 0)
    			event.getPlayer().damage(foodCost);

    		else
    			event.getPlayer().setFoodLevel(event.getPlayer().getFoodLevel() - foodCost);    		
    	}
        
        List<Block> blocks = event.getPlayer().getLastTwoTargetBlocks(null, 500);
        World world = event.getPlayer().getWorld();
        boolean motion = augments.containsKey("motion"); 
        Location location = ((Block)blocks.get(motion ? 1 : 0)).getLocation();
        world.playEffect(location, Effect.SMOKE, 1);
        world.playEffect(event.getPlayer().getLocation(), Effect.SMOKE, 1);
        location.add(0.5, 0, 0.5);
        if (motion)
        	location.add(0, 1, 0);
        event.getPlayer().teleport(location);
        
        return this;
    }
}
class Praemium extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Player player = event.getPlayer();
	    World wld = player.getWorld();
	    int w = 1;
	    if(!Vicincantatio.config.getBoolean("praemium_replaces_block", false))
	    {
	        w = 0;
	    }
	    List<Block> blocks = player.getLastTwoTargetBlocks(null, 500);
	    Block block = (Block)blocks.get(w);
	    blocks.clear();
	    blocks.add(block);
	    for (int i = 0; i < (int)(0.5*size*(size+1)) + 1; i++)
	    {
	    	Vector r = Vector.getRandom().subtract(new Vector(0.5, 0.5, 0.5)).normalize().multiply(3+size);
	    	Vector v = block.getLocation().toVector().add(r);
	    	Block b = wld.getBlockAt(v.toLocation(wld));
	    	blocks.add(b);
	    }
	    
	    float expSize = 4f + 0.5f*size;
	    boolean force = augments.containsKey("force");
	    if (force)
	    	expSize *= 1.5;
	    for (int i = 0; i < blocks.size(); i++)
	    {
	    	block = blocks.get(i);
            CraftWorld world = (CraftWorld)block.getWorld();
	        if (augments.containsKey("book"))
	        {
	            event.getPlayer().getWorld().createExplosion(block.getLocation(), expSize, force);
	            world.playEffect(block.getLocation(), Effect.SMOKE, 1);
	        } 
	        else
	        {
	            EntityTNTPrimed tnt = new EntityTNTPrimed(world.getHandle(), (float)block.getX() + 0.5F, (float)block.getY() + 0.5F, (float)block.getZ() + 0.5F);
	            tnt.yield = expSize;
	            world.getHandle().addEntity(tnt);
	            block.getWorld().playEffect(block.getLocation(), Effect.SMOKE, 1);
	        }
	    }
		return this;
	}
}
class Fulmen extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		boolean force = augments.containsKey("force");
		boolean book = augments.containsKey("book");
	    Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
	    int xorg = location.getBlockX();
	    int yorg = location.getBlockY();
	    int zorg = location.getBlockZ();
	    World world = event.getPlayer().getWorld();
	    ArrayList<Location> strikelocs = new ArrayList<Location>(60);
	    strikelocs.add(location);
	    int radius = 1 + (int)(0.45*size);
	    int nBolts = 1 + (int)(0.2*size*size + 0.015*size*size*size);
	    if(book)
	    {
	    	radius *= 5;
	    	nBolts = 5 + 5*nBolts + (int)(0.12*nBolts*nBolts);
	    }
	    for(int x = -radius; x <= radius; x++)
	    {
	        for(int y = -radius; y <= radius; y++)
	        {
	            for(int z = -radius; z <= radius; z++)
	            {
	            	if (Math.sqrt(x*x + y*y + z*z) <= radius)
	            	{
	                    Block block = world.getBlockAt(x+xorg, y+yorg, z+zorg);
	                    Location tloc = block.getLocation();
	                    if(block.getType() != Material.AIR && (block.getRelative(BlockFace.NORTH).getType() == Material.AIR ||
	                    			block.getRelative(BlockFace.SOUTH).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.EAST).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.WEST).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.UP).getType() == Material.AIR ||
		                    		block.getRelative(BlockFace.DOWN).getType() == Material.AIR))
	                        strikelocs.add(tloc);
	            	}
	            }
	        }
	    }
	    for(int m = 0; m < nBolts; m++)
	    {
	        int choice = (int)(Math.random() * (double)strikelocs.size());
	        Location loc = strikelocs.get(choice);
	        world.strikeLightning(loc);
	        if (force)
	        {
	        	world.getBlockAt(loc).getRelative(0, 1, 0).setType(Material.FIRE);
	        	if (book)
	        		world.createExplosion(loc, 3f);
	        }
	        if (nBolts-m > strikelocs.size())
	        	strikelocs.remove(choice);
	    }
	    return this;
	}
}
class Domus extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    player.getWorld().playEffect(player.getLocation(), Effect.SMOKE, 1);
	    player.teleport(player.getWorld().getSpawnLocation());
	    return this;
	}
}
class Sereno extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(false);
	    event.getPlayer().getWorld().setThundering(false);
	    return this;
	}
}
class Tempestas extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(true);
	    event.getPlayer().getWorld().setThundering(true);
	    return this;
	}
}
class Pluvia extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    event.getPlayer().getWorld().setStorm(true);
	    event.getPlayer().getWorld().setThundering(false);
	    return this;
	}
}
class Lucem extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		event.getPlayer().getWorld().setTime(23425L);
	    return this;
	}
}
class Noctem extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		event.getPlayer().getWorld().setTime(12550L);
	    return this;
	}
}

class Arbore extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    World world = event.getPlayer().getWorld();
	    Block block = event.getPlayer().getTargetBlock(null, 500);
	    //block.setType(Material.DIRT);
	    world.generateTree(block.getLocation().add(0, 1, 0), TreeType.TREE);
	    return this;
	}
}

class RealityCheck extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Material [][][] temp = new Material [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
		Block [][][] block = new Block [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		
	    for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					temp[i][j][k] = location.add(i, j, k).getBlock().getType();
					block[i][j][k] = location.add(i,j,k).getBlock();
				}
			}
		}
		
    	for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					block[j][i][k].setType(Material.STONE);  //setType(temp[i][j][k]);
				}
			}
		}
    	return this;
	}
}

class Vorto extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Material [][][] temp = new Material [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		Location location2 = location;
		
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
		Block [][][] block = new Block [(int)(size+2)][(int)(size+2)][(int)(size+2)];
		
	    for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					temp[i][j][k] = location2.add(i, j, k).getBlock().getType();
					location2=location.clone();
					block[i][j][k] = location2.add(i,j,k).getBlock();
					location2=location.clone();
				}
			}
		}
		
    	for (int i=0;i< (int)size+2; i++)
		{
			for (int j = 0; j<(int)size+2;j++)
			{
				for (int k =0; k < (int)size+2; k++)
				{
					block[j][i][k].setType(temp[i][j][k]);
				}
			}
		}
    	return this;
	}
}
class Urgeo extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
        String spell = "urgeo";
        Location location = ((Block)event.getPlayer().getLastTwoTargetBlocks(null, 500).get(0)).getLocation();
        Vector vector = event.getPlayer().getEyeLocation().getDirection();
        List<Entity> entities = event.getPlayer().getWorld().getEntities();
        for(Iterator<Entity> iterator = entities.iterator(); iterator.hasNext();)
        {
            Entity entity = iterator.next();
            if(location.distance(entity.getLocation()) <= 5D && 
            		(Vicincantatio.config.getBoolean((new StringBuilder(String.valueOf(spell))).append("_affects_players").toString(), true) ||
    				!(entity instanceof Player)) && 
    				(Vicincantatio.config.getBoolean(String.valueOf(spell)+"_affects_self", true) ||
					event.getPlayer().getEntityId() != entity.getEntityId()))
            {
                entity.setVelocity(vector.multiply(1D+size));
            }
        }
	    return this;
	}
}
class Vello extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Location location = ((Block)event.getPlayer().getLastTwoTargetBlocks(null, 500).get(0)).getLocation();
	    Vector vector = event.getPlayer().getEyeLocation().getDirection();
	    List<Entity> entities = event.getPlayer().getWorld().getEntities();
	    for(Iterator<Entity> iterator = entities.iterator(); iterator.hasNext();)
	    {
	        Entity entity = iterator.next();
	        if(location.distance(entity.getLocation()) <= 5D && (Vicincantatio.config.getBoolean("vello_affects_players", true) ||
	        		!(entity instanceof Player)) && (Vicincantatio.config.getBoolean("vello_affects_self", true) ||
    				event.getPlayer().getEntityId() != entity.getEntityId()))
	        {
	            entity.setVelocity(vector.multiply(-(1D+size)));
	        }
    	}
	    return this;
	}
}

class OstiumTartarus extends Spell
{
	 public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	 {
		 CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
		 Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
	  
		 location.getBlock().setType(Material.OBSIDIAN);
		 location.add(1, 0, 0).getBlock().setType(Material.OBSIDIAN);
	  	 location.add(0,1,0).getBlock().setType(Material.OBSIDIAN);
	 	 location.add(0,1,0).getBlock().setType(Material.OBSIDIAN);
	 	 location.add(0,1,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(0,1,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(-1,0,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(-1,0,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(-1,0,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(0,-1,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(0,-1,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(0,-1,0).getBlock().setType(Material.OBSIDIAN);
	     location.add(0,-1,0).getBlock().setType(Material.OBSIDIAN);
	  	 location.add(1,0,0).getBlock().setType(Material.OBSIDIAN);
	  
	  	 location.add(1,1,0).getBlock().setType(Material.FIRE);
	  
	     return this;
	 }
	 
}
class ChunkusErasus extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    World world = player.getWorld();
		Location location = player.getTargetBlock(null, 500).getLocation();
		Chunk chunk = world.getChunkAt(location);
		for (int y = 0; y < 128; y++)
			for (int x = 0; x < 16; x++)
				for (int z = 0; z < 16; z++)
					chunk.getBlock(x, y, z).setType(Material.AIR);
	    return this;
	}
}
class ChunkusRenero extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    World world = player.getWorld();
		Location location = player.getTargetBlock(null, 500).getLocation();
		Chunk chunk = world.getChunkAt(location);
		world.regenerateChunk(chunk.getX(), chunk.getZ());
	    return this;
	}
}
class Sagitto extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    World world = player.getWorld();
	    Vector dir = player.getEyeLocation().getDirection().normalize();
	    Vector pos = player.getEyeLocation().toVector().add(dir);
	    for (int i = 0; i < size*size/2 + 1; i++)
	    {
	    	Arrow a = world.spawnArrow(pos.toLocation(world), dir.multiply(50f), 600f, 10f+2.5f*size);
	    	a.teleport(pos.toLocation(world));
	    }
	    return this;
	}
}
class SagittoPluvia extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		location.setY(120);
		for (int i = 0; i < size*size*size*0.15 + size*size*5 + 8; i++)
		{
			Arrow a = event.getPlayer().getWorld().spawnArrow(location, new Vector(0, -10, 0), 600f, 10f+5f*size);
			double x = rand.nextGaussian() * (0.5+0.15*size);
			double z = rand.nextGaussian() * (0.5+0.15*size);
			a.setVelocity(new Vector(x, -10, z));
			a.teleport(location);
		}
	    return this;
	}
}
class Remedium extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Player player = event.getPlayer();
	    int health = player.getHealth();
	    if(augments.containsKey("book"))
	    {
	        player.setHealth(20);
	    } else
	    if(health + 6 < 20)
	    {
	        health += 6;
	        player.setHealth(health);
	    } else
	    {
	        health = 20;
	        player.setHealth(health);
	    }
	    return this;
	}
}
class Mutatio extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
	    World world = event.getPlayer().getWorld();
	    List<Entity> entities = world.getEntities();
	    for(Iterator<Entity> iterator = entities.iterator(); iterator.hasNext();)
	    {
	        Entity entity = iterator.next();
	        Location oloc = entity.getLocation();
	        if(location.distance(oloc) <= 5D && ((entity instanceof Creeper) || (entity instanceof Zombie) || (entity instanceof Skeleton) || (entity instanceof Spider)))
	        {
	            Location eloc = entity.getLocation();
	            if(eloc.getBlock().getType() == Material.WATER || eloc.getBlock().getType() == Material.STATIONARY_WATER)
	            {
	                CreatureType creatures[] = {
	                    CreatureType.CHICKEN, CreatureType.COW, CreatureType.SHEEP, CreatureType.SQUID
	                };
	                entity.remove();
	                world.playEffect(eloc, Effect.SMOKE, 1);
	                int i = (int)(Math.random() * (double)creatures.length);
	                CreatureType creature = creatures[i];
	                world.spawnCreature(eloc, creature);
	            } else
	            {
	                CreatureType creatures[] = {
	                    CreatureType.CHICKEN, CreatureType.COW, CreatureType.SHEEP
	                };
	                entity.remove();
	                world.playEffect(eloc, Effect.SMOKE, 1);
	                int i = (int)(Math.random() * (double)creatures.length);
	                CreatureType creature = creatures[i];
	                world.spawnCreature(eloc, creature);
	            }
	        }
	    }
	    return this;
	}
}

class Hadoken extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
    	Location location = event.getPlayer().getLocation();
    	Vector aim = event.getPlayer().getEyeLocation().getDirection().normalize();
    	
    	location.setX(location.getX() + aim.getX() * 4.0);
	 	location.setY(location.getY() + 0.5);
	 	location.setZ(location.getZ() + aim.getZ() * 4.0);
    	
    	for (int i = 0; i < 100; i++)
    	{
    		CraftArrow a = (CraftArrow) event.getPlayer().getWorld().spawnArrow(location, aim, 600f, 10f+5f*size);
    		a.setVelocity(aim.normalize().multiply(10F));
    		world.getHandle().addEntity(a.getHandle());
    	}
    	
    	return this;
	}
}
class Extinguere extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
	    if(event.getPlayer().getFireTicks() > 0)
	    {
	        event.getPlayer().getWorld().playEffect(event.getPlayer().getLocation(), Effect.SMOKE, 1);
	    }
	    event.getPlayer().setFireTicks(0);
	    return this;
	}
}


//Fireball
class GlobusIgnis extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		final boolean force = augments.containsKey("force");
		final boolean book = augments.containsKey("book");
		
		CraftWorld world = (CraftWorld)event.getPlayer().getWorld();
    	
		CraftPlayer craftPlayer = (CraftPlayer) event.getPlayer();
		EntityLiving playerEntity = craftPlayer.getHandle();
	 	
	 
	 	// Start it off a bit away from the player
	 	double distance = 4+0.5*size;
		
	 
	 	for (int i = 0; i < (int)(1.25*size*size) + 1; i++)
        {
        	Location location = event.getPlayer().getLocation();
    		Vector aim = event.getPlayer().getEyeLocation().getDirection().normalize();
	 		
	 		location.setX(location.getX() + aim.getX() * distance);
    	 	location.setY(location.getY() + (2.0) + 0.5);
    	 	location.setZ(location.getZ() + aim.getZ() * distance);
	 		
        	Vector r = Vector.getRandom().subtract(new Vector(0.5, 0.5, 0.5)).normalize().multiply(2+0.5*size);
        	Vector v = location.toVector().add(r);
        	
    		
        	EntityFireball fireball = new EntityFireball(((CraftWorld) craftPlayer.getWorld()).getHandle(), playerEntity, aim.getX(),aim.getY(),aim.getZ());
        	EntityFireball fireball1 = new EntityFireball(((CraftWorld) craftPlayer.getWorld()).getHandle(), playerEntity, aim.getX(),aim.getY(),aim.getZ());
        	
        	fireball.setDirection(aim.getX(), aim.getY(), aim.getZ());
        	
        	fireball.locX = v.getX();
        	fireball.locY = v.getY();
        	fireball.locZ = v.getZ();
        	
//        	fireball.motX = aim.getX() * .1;
//    	 	fireball.motY = aim.getY() * .1;
//    	 	fireball.motZ = aim.getZ() * .1;
        	
        	((CraftWorld) craftPlayer.getWorld()).getHandle().addEntity(fireball);
        	
        	if(force)
        	{
        		//background work
        		location = event.getPlayer().getLocation();
        		
        		location.setX(location.getX() - aim.getX() * distance);
        	 	location.setY(location.getY() + (2.0) + 0.5);
        	 	location.setZ(location.getZ() - aim.getZ() * distance);
            	v = location.toVector().subtract(r);
        		
        		//shoot behind ya as well
        		aim.multiply(-1.);
        		fireball1.setDirection(aim.getX(),aim.getY(),aim.getZ());
        		fireball1.locX = v.getX();
            	fireball1.locY = v.getY();
            	fireball1.locZ = v.getZ();
            	
            	((CraftWorld) craftPlayer.getWorld()).getHandle().addEntity(fireball1);
        	}
        	
        }
		
		return this;
	}
}


class Starve extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		World world = event.getPlayer().getWorld();
		List<Entity> entities = world.getEntities();
		for(Iterator<Entity> iterator = entities.iterator(); iterator.hasNext();)
		{
			Entity entity = iterator.next();
			Location oloc = entity.getLocation();
			//			Distance clause					is player						not self
         	if(location.distance(oloc) <= 5D && ((entity instanceof Player)) && entity != event.getPlayer())
         		((CraftPlayer) entity).setFoodLevel((int)(.4*((CraftPlayer) entity).getFoodLevel()));
      	}
		//spell code goes here
	    return this;
	}
}

class leap extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Vector aim = event.getPlayer().getEyeLocation().getDirection().normalize();
		
		event.getPlayer().setVelocity(aim.multiply(10));
		
		return this;
	}
}

class IgnisPerpetua extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		int w = 1;
        if(!Vicincantatio.config.getBoolean("ignis_perpetua_replaces_block", false))
        {
            w = 0;
        }
        Block block = (Block)event.getPlayer().getLastTwoTargetBlocks(null, 500).get(w);
        Block aboveblock = block.getFace(BlockFace.UP);
        Material type = block.getType();
        Material abovetype = aboveblock.getType();
        if(type != Material.BEDROCK && abovetype != Material.BEDROCK)
        {
            block.setType(Material.NETHERRACK);
            block.getWorld().playEffect(block.getLocation(), Effect.SMOKE, 1);
            aboveblock.setType(Material.FIRE);
            aboveblock.getWorld().playEffect(aboveblock.getLocation(), Effect.SMOKE, 1);
        }
		
		return this;
	}
	
}


class SummonGiant extends Spell
{
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		Location location = event.getPlayer().getTargetBlock(null, 500).getLocation();
		World world = event.getPlayer().getWorld();
		
		world.spawnCreature(location, CreatureType.GIANT);
		
		return this;
	}
	
}

	

/*
 *		Adding spells
 *		class NewSpell extends Spell
 *
 *  	in VicincantatioSpell, keep Map<String, Spell> spellTypes
 *  	when casting a spell, spellTypes.get("spellName").CastSpell(params);
 *  	if needing an over-time reference, spellTypes.get("spellName").InstantiateSpell(params);
 */


/*
 *  Using Food Cost
 *  Everything is inherited (thanx Jacob)
 *  
 *  //first, if enabled
 *	if(FoodEnabled())
 *	{
 *		//this is an int from 1 to 20, each int is 1/2 an icon
 *		foodCost = 1;
 *		
 *		//This part is if player is starving and still casts spell; it deals that much to hp; same style as food, int 1 to 20
 *		if(event.getPlayer().getFoodLevel() <= 0)
 *			event.getPlayer().damage(foodCost);
 *
 *		//This part is for a fed player
 *		else
 *			event.getPlayer().setFoodLevel(event.getPlayer().getFoodLevel() - foodCost);    		
 *	}
*/